#!/usr/bin/perl -w # useful for syntax highlighting

package clreqd_modules;

use Fcntl;
use cldb;
use clstatdb;
use Sys::Hostname;
use Data::Dumper;

# The purpose of this request is to look at all the validated disk
# deartbeats configured and load in all records for those.
# It then reads the blocks necessary from each offset for each active
# host in the cluster.
#
# It will then wait interval+5 seconds and will then re-read the data from the
# raw devices. Any that have not changed will be shown as failed, otherwise
# OK.

sub diskhb_check {
my %ARGS=@_;

	my $hostname=hostname;
	# Open the configuration database and read in all details
	# of validated disk entries...
	my $db=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($db)) {
		return "ERROR Unable to open configuration database.";
	}

	my $h=$db->get_rows("SELECT value FROM globals WHERE var = 'interval';");
	my $interval;
	if(!defined($h->[0]->{value})) {
		my $interval=2;
	} else {
		$interval=int($h->[0]->{value});
	}
	$interval+=6;
	$h=$db->get_rows("SELECT * FROM diskhb WHERE validated = '1';");
	$db->disconnect;

	my @HBS=();
	for my $crow (@$h) {
		if(defined($crow->{write_offset})) {
			if(! grep {$_ eq $crow->{name}} @HBS) {
				push @HBS,$crow->{name};
			}
		}
	}

	# We need to work out which hosts are currently active in the
	# cluster so we can ignore all the others...

	$db=new clstatdb("$::ENV{TRUECL_CFG}/status.db");
	if(!defined($db)) {
		return "ERROR Unable to open status database.";
	}
	my $h2=$db->get_rows("SELECT * FROM cluster_status_vars WHERE value = 'Joined';");
	$db->disconnect;

	my @HOSTS=();
	for my $crow (@$h2) {
		if(defined($crow->{var}) && $crow->{var} =~ /(.*?):clusternodestate$/) {
			my $n=$1;
			if(! grep {$_ eq $n} @HOSTS) {
				push @HOSTS,$n;
			}
		}
	}

	my %first_read=();

	# Ok - we need to read details from each heartbeat device using the local
	# copy...

	for my $chb (@HBS) {
		$first_read{$chb}={};
		my $dev=undef;
		my $len=undef;
		for my $crow (@$h) {
			if($crow->{node} eq $hostname && $crow->{name} eq $chb) {
				$dev=$crow->{devpath};
				$len=int($crow->{blksize});
				last;
			}
		}
		if(!defined($dev)) {
			::warning("Now device found for heartbeat '$chb' on host '$hostname' - ignoring from heartbeat checks.");
			next;
		}
		for my $crow (@$h) {
			if($crow->{name} eq $chb) {
				my $offset=int($crow->{write_offset});
				my $data=_diskhb_check_read($dev,$offset,$len);
				# ::warning("1st:dev=$dev,offset=$offset,len=$len");
				$first_read{$chb}->{$crow->{node}}=$data;
			}
		}
	}
	# Wait a little while before repeating the process...

	select(undef,undef,undef,$interval);

	my %last_read=();
	for my $chb (@HBS) {
		$last_read{$chb}={};
		my $dev=undef;
		my $len=undef;
		for my $crow (@$h) {
			if($crow->{node} eq $hostname && $crow->{name} eq $chb) {
				$dev=$crow->{devpath};
				$len=int($crow->{blksize});
				last;
			}
		}
		if(!defined($dev)) {
			::warning("Now device found for heartbeat '$chb' on host '$hostname' - ignoring from heartbeat checks.");
			next;
		}
		for my $crow (@$h) {
			if($crow->{name} eq $chb) {
				my $offset=int($crow->{write_offset});
				my $data=_diskhb_check_read($dev,$offset,$len);
				# ::warning("2nd:dev=$dev,offset=$offset,len=$len");
				$last_read{$chb}->{$crow->{node}}=$data;
			}
		}
	}
	# ::warning(Dumper(\%first_read));
	# ::warning(Dumper(\%last_read));

	# Ok, now we need to compare the results for each heartbeat for the
	# active hosts in the cluster.

	my @R=();
	for my $chb (@HBS) {
		for $chost (@HOSTS) {
			# Firstly check to see if an error is present in first/last
			# read and handle accordingly.
			my ($rc,$data)=($first_read{$chb}->{$chost} =~ /(.*?)\s+(.*)/);
			my ($rc2,$data2)=($last_read{$chb}->{$chost} =~ /(.*?)\s+(.*)/);
			if($rc eq "ERROR") {
				push @R,"$chb:$chost $first_read{$chb}->{$chost}";
				next;
			}
			if($rc2 eq "ERROR") {
				push @R,"$chb:$chost $last_read{$chb}->{$chost}";
				next;
			}
			# Ensure the data has changed and moan if not.
			if($data eq $data2) {
				push @R,"$chb:$chost ERROR Heartbeat not updated.";
				next;
			}
			push @R,"$chb:$chost OK";
		}
	}
	return "OK\n" . join("\n",@R);
}

sub _diskhb_check_read($$$) {
my $fd;
my $devname=shift;
my $offset=shift;
my $length=shift;

	sysopen($fd,$devname,O_RDWR);
	if(! $fd) {
		return "ERROR Unable to open device '$devname': $!";
	}
	if(sysseek($fd,$offset,0) != $offset) {
		return "ERROR Unable to seek to offset '$offset' on '$devname'.";
	}
	my $d;
	if(sysread($fd,$d,$length) != $length) {
		return "ERROR Unable to read '$length' bytes of data from '$devname'.";
	}
	close($fd);
	return "OK " . unpack("H*",$d);
}

1;
